home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / sbin / pam-auth-update < prev    next >
Encoding:
Text File  |  2009-09-03  |  18.3 KB  |  682 lines

  1. #!/usr/bin/perl -w
  2.  
  3. # pam-auth-update: update /etc/pam.d/common-* from /usr/share/pam-configs
  4. #
  5. # Update the /etc/pam.d/common-* files based on the per-package profiles
  6. # provided in /usr/share/pam-configs/ taking into consideration user's
  7. # preferences (as determined via debconf prompting).
  8. #
  9. # Written by Steve Langasek <steve.langasek@canonical.com>
  10. #
  11. # Copyright (C) 2008 Canonical Ltd.
  12. #
  13. # This program is free software; you can redistribute it and/or modify
  14. # it under the terms of version 3 of the GNU General Public License as
  15. # published by the Free Software Foundation.
  16. #
  17. # # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. # GNU General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU General Public License
  23. # along with this program; if not, write to the Free Software
  24. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
  25. # USA.
  26.  
  27. use strict;
  28. use Debconf::Client::ConfModule ':all';
  29. use IPC::Open2 'open2';
  30.  
  31. version('2.0');
  32. my $capb=capb('backup');
  33.  
  34. my $inputdir = '/usr/share/pam-configs';
  35. my $template = 'libpam-runtime/profiles';
  36. my $errtemplate = 'libpam-runtime/conflicts';
  37. my $overridetemplate = 'libpam-runtime/override';
  38. my $blanktemplate = 'libpam-runtime/no_profiles_chosen';
  39. my $confdir = '/etc/pam.d';
  40. my $savedir = '/var/lib/pam';
  41. my (%profiles, @sorted, @enabled, @conflicts, %removals);
  42. my $force = 0;
  43. my $priority = 'high';
  44. my %md5sums = (
  45.     'auth' => [
  46.         '8d4fe17e66ba25de16a117035d1396aa',
  47.         '1fd1e8e87cef1c13898410d830229122',
  48.     ],
  49.     'account' => [
  50.         '3c0c362eaf3421848b679d63fd48c3fa',
  51.         '8a29dc79152ce8441aa90a8f8650d076',
  52.     ],
  53.     'password' => [
  54.         '4bd7610f2e85f8ddaef79c7db7cb49eb',
  55.         '50fce2113dfda83ac8bdd5a6e706caec',
  56.         '9ba753d0824276b44bcadfee1f87b6bc',
  57.         '86180c1552203d9b58582cf547309d01',
  58.         '6d14efb5c3306c6896b9acf434932808',
  59.     ],
  60.     'session' => [
  61.         '240fb92986c885b327cdb21dd641da8c',
  62.         '1bd2f3e86f552c57f5ee013b93ffca2b',
  63.         '06cffe624c9bb7d9a7b5891c8a0f94b2',
  64.         '4a25673e8b36f1805219027d3be02cd2',
  65.     ],
  66. );
  67.  
  68. opendir(DIR, $inputdir) || die "could not open config directory: $!";
  69. while (my $profile = readdir(DIR)) {
  70.     next if ($profile eq '.' || $profile eq '..');
  71.     %{$profiles{$profile}} = parse_pam_profile($inputdir . '/' . $profile);
  72. }
  73. closedir DIR;
  74.  
  75. # use a '--force' arg to specify that /etc/pam.d should be overwritten; 
  76. # used only on upgrades where the postinst has already determined that the
  77. # checksums match.  Module packages other than libpam-runtime itself must
  78. # NEVER use this option!  Document with big skullses and crossboneses!  It
  79. # needs to be exposed for libpam-runtime because that's the package that
  80. # decides whether we have a pristine config to be converted, and knows
  81. # whether the version being upgraded from is one for which the conversion
  82. # should be done.
  83.  
  84. while ($#ARGV >= 0) {
  85.     my $opt = shift;
  86.     if ($opt eq '--force') {
  87.         $force = 1;
  88.     } elsif ($opt eq '--package') {
  89.         $priority = 'medium';
  90.     } elsif ($opt eq '--remove') {
  91.         while ($#ARGV >= 0) {
  92.             last if ($ARGV[0] =~ /^--/);
  93.             $removals{shift @ARGV} = 1;
  94.         }
  95.         # --remove implies --package
  96.         $priority = 'medium' if (keys(%removals));
  97.     }
  98. }
  99.  
  100. x_loadtemplatefile('/var/lib/dpkg/info/libpam-runtime.templates','libpam-runtime');
  101.  
  102. # always sort by priority, so we have consistency and don't have to
  103. # shuffle later
  104. @sorted = sort { $profiles{$b}->{'Priority'} <=> $profiles{$a}->{'Priority'}
  105.                  || $b cmp $a }
  106.                keys(%profiles);
  107. # If we're being called for package removal, filter out those options here
  108. @sorted = grep { !$removals{$_} } @sorted;
  109.  
  110. subst($template, 'profile_names', join(', ',@sorted));
  111. subst($template, 'profiles',
  112.     join(', ', map { $profiles{$_}->{'Name'} } @sorted));
  113.  
  114. my $diff = diff_profiles($confdir,$savedir);
  115.  
  116. if ($diff) {
  117.     @enabled = grep { !$removals{$_} } @{$diff->{'mods'}};
  118. } else {
  119.     @enabled = split(/, /,get($template));
  120. }
  121.  
  122. # find out what we've seen, so we can ignore those defaults
  123. my %seen;
  124. if (-e $savedir . '/seen') {
  125.     open(SEEN,$savedir . '/seen');
  126.     while (<SEEN>) {
  127.         chomp;
  128.         $seen{$_} = 1;
  129.     }
  130.     close(SEEN);
  131. }
  132.  
  133. # filter out any options that are no longer available for any reason
  134. @enabled = grep { $profiles{$_} } @enabled;
  135.  
  136. # an empty module set is an error, so in that case grab all the defaults
  137. if (!@enabled) {
  138.     %seen = ();
  139.     $priority = 'high' unless ($force);
  140. }
  141.  
  142. # add any previously-unseen configs
  143. push(@enabled,
  144.      grep { $profiles{$_}->{'Default'} eq 'yes' && !$seen{$_} } @sorted);
  145. @enabled = sort { $profiles{$b}->{'Priority'} <=> $profiles{$a}->{'Priority'}
  146.                   || $b cmp $a }
  147.                 @enabled;
  148. my $prev = '';
  149. @enabled = grep { $_ ne $prev && (($prev) = $_) } @enabled;
  150.  
  151.  
  152. fset($template,'seen','false');
  153. set($template,join(', ', @enabled));
  154.  
  155. # if diff_profiles() fails, and we weren't passed a 'force' argument
  156. # (because this isn't an upgrade from an old version, or the checksum
  157. # didn't match, or we're being called by some other module package), prompt
  158. # the user whether to override.  If the user declines (the default), we
  159. # never again manage this config unless manually called with '--force'.
  160. if (!$diff && !$force) {
  161.     input('high',$overridetemplate);
  162.     go();
  163.     $force = 1 if (get($overridetemplate) eq 'true');
  164. }
  165.  
  166. if (!$diff && !$force) {
  167.     print STDERR <<EOF;
  168.  
  169. pam-auth-update: Local modifications to /etc/pam.d/common-*, not updating.
  170. pam-auth-update: Run pam-auth-update --force to override.
  171.  
  172. EOF
  173.     exit;
  174. }
  175.  
  176. umask(0022);
  177.  
  178. do {
  179.     @conflicts = ();
  180.     input($priority,$template);
  181.     go();
  182.  
  183.     @enabled = split(/, /, get($template));
  184.  
  185.     # in case of conflicts, automatically unset the lower priority
  186.     # item of each pair
  187.     foreach my $elem (@enabled)
  188.     {
  189.         for (my $i=$#enabled; $i >= 0; $i--)
  190.         {
  191.             my $conflict = $enabled[$i];
  192.             if ($profiles{$elem}->{'Conflicts'}->{$conflict}) {
  193.                 splice(@enabled,$i,1);
  194.                 my $desc = $profiles{$elem}->{'Name'}
  195.                     . ', ' . $profiles{$conflict}->{'Name'};
  196.                 push(@conflicts,$desc);
  197.             }
  198.         }
  199.     }
  200.     if (@conflicts) {
  201.         subst($errtemplate, 'conflicts', join("\n", @conflicts));
  202.         input('high',$errtemplate);
  203.     }
  204.     fset($template,'seen','false');
  205.     set($template, join(', ', @enabled));
  206.     if (!@enabled) {
  207.         input('high',$blanktemplate);
  208.     }
  209. } while (@conflicts || !@enabled);
  210.  
  211. # the decision has been made about what configs to use, so even if
  212. # something fails after this, we shouldn't go munging the default
  213. # options again.  Save the list of known configs to /var/lib/pam.
  214. open(SEEN,"> $savedir/seen");
  215. for my $i (@sorted) {
  216.     print SEEN "$i\n";
  217. }
  218. close(SEEN);
  219.  
  220. # @enabled now contains our list of profiles to use for piecing together
  221. # a config
  222. # we have:
  223. # - templates into which we insert the specialness
  224. # - magic comments denoting the beginning and end of our managed block;
  225. #   looking at only the functional config lines would potentially let us
  226. #   handle more cases, at the expense of much greater complexity, so
  227. #   pass on this at least for the first round
  228. # - a representation of the autogenerated config stored in /var/lib/pam,
  229. #   that we can diff against in order to account for changed options or
  230. #   manually dropped modules
  231. # - a hash describing the local modifications the user has made to the
  232. #   config; these are always preserved unless manually overridden with
  233. #   the --force option
  234.  
  235. write_profiles(\%profiles, \@enabled, $confdir, $savedir, $diff, $force);
  236.  
  237.  
  238. # take a single line from a stock config, and merge it with the
  239. # information about local admin edits
  240. sub merge_one_line
  241. {
  242.     my ($line,$diff,$count) = @_;
  243.     my (@opts,$modline);
  244.  
  245.     my ($adds,$removes);
  246.  
  247.     $line =~ /^((\[[^]]+\]|\w+)\s+\S+)\s*(.*)/;
  248.  
  249.     @opts = split(/\s+/,$3);
  250.     $modline = $1;
  251.     $modline =~ s/end/$count/g;
  252.     if ($diff) {
  253.         my $mod = $modline;
  254.         $mod =~ s/[0-9]+//g;
  255.         $adds = \%{$diff->{'add'}{$mod}};
  256.         $removes = \%{$diff->{'remove'}{$mod}};
  257.     } else {
  258.         $adds = $removes = undef;
  259.     }
  260.  
  261.     for (my $i = 0; $i <= $#opts; $i++) {
  262.         if ($adds->{$opts[$i]}) {
  263.             delete $adds->{$opts[$i]};
  264.         }
  265.         if ($removes->{$opts[$i]}) {
  266.             splice(@opts,$i,1);
  267.             $i--;
  268.         }
  269.     }
  270.     return $modline . " " . join(' ',@opts,keys(%{$adds})) . "\n";
  271. }
  272.  
  273. # return the lines for a given config name, type, and position in the stack
  274. sub lines_for_module_and_type
  275. {
  276.     my ($profiles, $mod, $type, $modpos) = @_;
  277.     if ($modpos == 0 && $profiles->{$mod}{$type . '-Initial'}) {
  278.         return $profiles->{$mod}{$type . '-Initial'};
  279.     }
  280.     return $profiles->{$mod}{$type};
  281. }
  282.  
  283. # create a single PAM config from the indicated template and selections,
  284. # writing to a new file
  285. sub create_from_template
  286. {
  287.     my($template,$dest,$profiles,$enabled,$diff,$type) = @_;
  288.     my $state = 0;
  289.     my $uctype = ucfirst($type);
  290.  
  291.     open(INPUT,$template) || return 0;
  292.     open(OUTPUT,">$dest") || return 0;
  293.  
  294.     while (<INPUT>) {
  295.         if ($state == 1) {
  296.             if (/^# here's the fallback if no module succeeds/) {
  297.                 print OUTPUT;
  298.                 $state++;
  299.             }
  300.             next;
  301.         }
  302.         if ($state == 3) {
  303.             if (/^# end of pam-auth-update config/) {
  304.                 print OUTPUT;
  305.                 $state++;
  306.             }
  307.             next;
  308.         }
  309.  
  310.         print OUTPUT;
  311.  
  312.         my ($pattern,$val);
  313.         if ($state == 0) {
  314.             $pattern = '^# here are the per-package modules \(the "Primary" block\)';
  315.             $val = 'Primary';
  316.         } elsif ($state == 2) {
  317.             $pattern = '^# and here are more per-package modules \(the "Additional" block\)';
  318.             $val = 'Additional';
  319.         } else {
  320.             next;
  321.         }
  322.  
  323.         if (/$pattern/) {
  324.             my $i = 0;
  325.             my $count = 0;
  326.             # first we need to get a count of lines that we're
  327.             # going to output, so we can fix up the jumps correctly
  328.             for my $mod (@{$enabled}) {
  329.                 my $output;
  330.                 next if (!$profiles->{$mod}{$uctype . '-Type'});
  331.                 next if $profiles->{$mod}{$uctype . '-Type'} ne $val;
  332.                 $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
  333.                 # bypasses a perl warning about @_, sigh
  334.                 my @tmparr = split("\n+",$output);
  335.                 $count += @tmparr;
  336.             }
  337.  
  338.             # in case anything tries to jump in the 'additional'
  339.             # block, let's try not to jump off the stack...
  340.             $count-- if ($val eq 'Additional');
  341.  
  342.             # no primary block, so output a stock pam_permit line
  343.             # to keep the stack intact
  344.             if ($val eq 'Primary' && $count == 0)
  345.             {
  346.                 print OUTPUT "$type\t[default=1]\t\t\tpam_permit.so\n";
  347.             }
  348.  
  349.             $i = 0;
  350.             for my $mod (@{$enabled}) {
  351.                 my $output;
  352.                 my @output;
  353.                 next if (!$profiles->{$mod}{$uctype . '-Type'});
  354.                 next if $profiles->{$mod}{$uctype . '-Type'} ne $val;
  355.                 $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
  356.                 for my $line (split("\n",$output)) {
  357.                     $line = merge_one_line($line,$diff,
  358.                                            $count);
  359.                     print OUTPUT "$type\t$line";
  360.                     $count--;
  361.                 }
  362.             }
  363.             $state++;
  364.         }
  365.     }
  366.     close(INPUT);
  367.     close(OUTPUT);
  368.  
  369.     if ($state < 4) {
  370.         unlink($dest);
  371.         return 0;
  372.     }
  373.     return 1;
  374. }
  375.  
  376. # take a template file, strip out everything between the markers, and
  377. # return the md5sum of the remaining contents.  Used for testing for
  378. # local modifications of the boilerplate.
  379. sub get_template_md5sum
  380. {
  381.     my($template) = @_;
  382.     my $state = 0;
  383.  
  384.     open(INPUT,$template) || return '';
  385.     my($md5sum_fd,$output_fd);
  386.     my $pid = open2($md5sum_fd, $output_fd, 'md5sum');
  387.     return '' if (!$pid);
  388.  
  389.     while (<INPUT>) {
  390.         if ($state == 1) {
  391.             if (/^# here's the fallback if no module succeeds/) {
  392.                 print $output_fd $_;
  393.                 $state++;
  394.             }
  395.             next;
  396.         }
  397.         if ($state == 3) {
  398.             if (/^# end of pam-auth-update config/) {
  399.                 print $output_fd $_;
  400.                 $state++;
  401.             }
  402.             next;
  403.         }
  404.  
  405.         print $output_fd $_;
  406.  
  407.         my ($pattern,$val);
  408.         if ($state == 0) {
  409.             $pattern = '^# here are the per-package modules \(the "Primary" block\)';
  410.         } elsif ($state == 2) {
  411.             $pattern = '^# and here are more per-package modules \(the "Additional" block\)';
  412.         } else {
  413.             next;
  414.         }
  415.  
  416.         if (/$pattern/) {
  417.             $state++;
  418.         }
  419.     }
  420.     close(INPUT);
  421.     close($output_fd);
  422.     my $md5sum = <$md5sum_fd>;
  423.     close($md5sum_fd);
  424.     waitpid $pid, 0;
  425.  
  426.     $md5sum = (split(/\s+/,$md5sum))[0];
  427.     return $md5sum;
  428. }
  429.  
  430. # merge a set of module declarations into a set of new config files,
  431. # using the information returned from diff_profiles().
  432. sub write_profiles
  433. {
  434.     my($profiles,$enabled,$confdir,$savedir,$diff,$force) = @_;
  435.  
  436.     if (! -d $savedir) {
  437.         mkdir($savedir);
  438.     }
  439.         
  440.     # because we can't atomically replace both /var/lib/pam/$foo and
  441.     # /etc/pam.d/common-$foo at the same time, take steps to make this
  442.     # somewhat robust
  443.     for my $type ('auth','account','password','session') {
  444.         my $target = $confdir . '/common-' . $type;
  445.         my $template = $target;
  446.         my $dest = $template . '.pam-new';
  447.  
  448.         my $diff = $diff;
  449.         if ($diff) {
  450.             $diff = \%{$diff->{$type}};
  451.         }
  452.  
  453.         # Detect if the template is unmodified, and if so, use
  454.         # the version from /usr/share.  Depends on knowing the
  455.         # md5sums of the originals.
  456.         my $md5sum = get_template_md5sum($template);
  457.         for my $i (@{$md5sums{$type}}) {
  458.             if ($md5sum eq $i) {
  459.                 $template = '/usr/share/pam/common-' . $type;
  460.                 last;
  461.             }
  462.         }
  463.  
  464.         # first, write out the new config
  465.         if (!create_from_template($template,$dest,$profiles,$enabled,
  466.                                   $diff,$type))
  467.         {
  468.             if (!$force) {
  469.                 return 0;
  470.             }
  471.             $template = '/usr/share/pam/common-' . $type;
  472.             if (!create_from_template($template,$dest,$profiles,
  473.                                       $enabled,$diff,$type))
  474.             {
  475.                 return 0;
  476.             }
  477.         }
  478.  
  479.         # then write out the saved config
  480.         if (!open(OUTPUT, "> $savedir/$type.new")) {
  481.             unlink($dest);
  482.             return 0;
  483.         }
  484.         my $i = 0;
  485.         my $uctype = ucfirst($type);
  486.         for my $mod (@{$enabled}) {
  487.             my $output;
  488.             next if (!$profiles->{$mod}{$uctype . '-Type'});
  489.             next if ($profiles->{$mod}{$uctype . '-Type'} eq 'Additional');
  490.  
  491.             $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
  492.             if ($output) {
  493.                 print OUTPUT "Module: $mod\n";
  494.                 print OUTPUT $output . "\n";
  495.             }
  496.         }
  497.  
  498.         # no primary block, so output a stock pam_permit line
  499.         if ($i == 0)
  500.         {
  501.             print OUTPUT "Module: null\n";
  502.             print OUTPUT "[default=1]\t\t\tpam_permit.so\n";
  503.         }
  504.  
  505.         $i = 0;
  506.         for my $mod (@{$enabled}) {
  507.             my $output;
  508.             next if (!$profiles->{$mod}{$uctype . '-Type'});
  509.             next if ($profiles->{$mod}{$uctype . '-Type'} eq 'Primary');
  510.  
  511.             $output = lines_for_module_and_type($profiles, $mod, $uctype, $i++);
  512.             if ($output) {
  513.                 print OUTPUT "Module: $mod\n";
  514.                 print OUTPUT $output . "\n";
  515.             }
  516.         }
  517.  
  518.         close(OUTPUT);
  519.  
  520.         # then do the renames, back-to-back
  521.         # we have to use system because File::Copy is in
  522.         # perl-modules, not perl-base
  523.         if (-e "$target" && $force) {
  524.             system('cp','-f',$target,$target . '.pam-old');
  525.         }
  526.         rename($dest,$target);
  527.         rename("$savedir/$type.new","$savedir/$type");
  528.     }
  529.  
  530.     # at the end of a successful write, reset the 'seen' flag and the
  531.     # value of the debconf override question.
  532.     fset($overridetemplate,'seen','false');
  533.     set($overridetemplate,'false');
  534. }
  535.  
  536. # reconcile the current config in /etc/pam.d with the saved ones in
  537. # /var/lib/pam; returns a hash of profile names and the corresponding
  538. # options that should be added/removed relative to the stock config.
  539. # returns false if any of the markers are missing that permit a merge,
  540. # or on any other failure.
  541. sub diff_profiles
  542. {
  543.     my ($sourcedir,$savedir) = @_;
  544.     my (%diff);
  545.  
  546.     @{$diff{'mods'}} = ();
  547.     # Load the saved config from /var/lib/pam, then iterate through all
  548.     # lines in the current config that are in the managed block.
  549.     # If anything fails here, just return immediately since we then
  550.     # have nothing to merge; instead, the caller will decide later
  551.     # whether to force an overwrite.
  552.     for my $type ('auth','account','password','session') {
  553.         my (@saved,$modname);
  554.  
  555.         open(SAVED,$savedir . '/' . $type) || return 0;
  556.         while (<SAVED>) {
  557.             if (/^Module: (.*)/) {
  558.                 $modname = $1;
  559.                 next;
  560.             }
  561.             chomp;
  562.             # trim out the destination of any jumps; this saves
  563.             # us from having to re-parse everything just to fix
  564.             # up the jump lengths, when changes to these will
  565.             # already show up as inconsistencies elsewhere
  566.             s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
  567.             s/(\[.*)end(.*\])/$1$2/g;
  568.             my (@temp) = ($modname,$_);
  569.             push(@saved,\@temp);
  570.         }
  571.         close(SAVED);
  572.  
  573.         my $state = 0;
  574.         my (@prev_opts,$curmod);
  575.  
  576.         open(CURRENT,$sourcedir . '/common-' . $type) || return 0;
  577.         while (<CURRENT>) {
  578.             if ($state == 0) {
  579.                 $state = 1
  580.                    if (/^# here are the per-package modules \(the "Primary" block\)/);
  581.                 next;
  582.             }
  583.             if ($state == 1) {
  584.                 s/^$type\s+//;
  585.                 if (/^# here's the fallback if no module succeeds/) {
  586.                     $state = 2;
  587.                     next;
  588.                 }
  589.             }
  590.             if ($state == 2) {
  591.                 $state = 3
  592.                    if (/^# and here are more per-package modules \(the "Additional" block\)/);
  593.                 next;
  594.             }
  595.             if ($state == 3) {
  596.                 last if (/^# end of pam-auth-update config/);
  597.                 s/^$type\s+//;
  598.             }
  599.  
  600.             my $found = 0;
  601.             my $curopts;
  602.             while (!$found && $#saved >= 0) {
  603.                 my $line;
  604.                 ($modname,$line) = @{$saved[0]};
  605.                 shift(@saved);
  606.                 $line =~ /^((\[[^]]+\]|\w+)\s+\S+)\s*(.*)/;
  607.                 @prev_opts = split(/\s+/,$3);
  608.                 $curmod = $1;
  609.                 # FIXME: the key isn't derived from the config
  610.                 # name, so collisions are possible if more
  611.                 # than one config references the same module
  612.  
  613.                 $_ =~ s/(\[[^0-9]*)[0-9]+(.*\])/$1$2/g;
  614.                 # check if this is a match for the current line
  615.                 if ($_ =~ /^\Q$curmod\E\s*(.*)$/) {
  616.                     $found = 1;
  617.                     $curopts = $1;
  618.                     push(@{$diff{'mods'}},$modname);
  619.                 }
  620.             }
  621.  
  622.             # there's a line in the live config that doesn't
  623.             # correspond to anything from the saved config.
  624.             # treat this as a failure; it's very error-prone
  625.             # to decide what to do with an added line that
  626.             # didn't come from a package.
  627.             return 0 if (!$found);
  628.  
  629.             for my $opt (split(/\s+/,$curopts)) {
  630.                 my $found = 0;
  631.                 for (my $i = 0; $i <= $#prev_opts; $i++) {
  632.                     if ($prev_opts[$i] eq $opt) {
  633.                         $found = 1;
  634.                         splice(@prev_opts,$i,1);
  635.                     }
  636.                 }
  637.                 $diff{$type}{'add'}{$curmod}{$opt} = 1 if (!$found);
  638.             }
  639.             for my $opt (@prev_opts) {
  640.                 $diff{$type}{'remove'}{$curmod}{$opt} = 1;
  641.             }
  642.         }
  643.         close(CURRENT);
  644.  
  645.         # we couldn't parse the config, so the merge fails
  646.         return 0 if ($state < 3);
  647.     }
  648.     return \%diff;
  649. }
  650.  
  651. # simple function to parse a provided config file, in pseudo-RFC822
  652. # format,
  653. sub parse_pam_profile
  654. {
  655.     my ($profile) = $_[0];
  656.     my $fieldname;
  657.     my %profile;
  658.     open(PROFILE, $profile) || die "could not read profile $profile: $!";
  659.     while (<PROFILE>) {
  660.         if (/^(\S+):\s+(.*)$/) {
  661.             $fieldname = $1;
  662.             # compatibility with the first implementation round;
  663.             # "Auth-Final" is now just called "Auth"
  664.             $fieldname =~ s/-Final$//;
  665.             if ($fieldname eq 'Conflicts') {
  666.                 foreach my $elem (split(/, /, $2)) {
  667.                     $profile{'Conflicts'}->{$elem} = 1;
  668.                 }
  669.             } else {
  670.                 $profile{$fieldname} = $2;
  671.             }
  672.         } else {
  673.             chomp;
  674.             s/^\s+//;
  675.             $profile{$fieldname} .= "\n$_";
  676.             $profile{$fieldname} =~ s/^[\n\s]+//;
  677.         }
  678.     }
  679.     close(PROFILE);
  680.     return %profile;
  681. }
  682.